package org.tigr.htc.cmd;

/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.tigr.antware.shared.command.events.CommandInterrupt;
import org.tigr.antware.shared.command.events.CommandLifetimeLI;
import org.tigr.antware.shared.command.events.CommandRuntimeStatusLI;
import org.tigr.antware.shared.command.events.CommandStatusLI;
import org.tigr.antware.shared.command.events.FailureEvent;
import org.tigr.antware.shared.command.events.FinishEvent;
import org.tigr.antware.shared.command.events.InterruptEvent;
import org.tigr.antware.shared.command.events.ResumeEvent;
import org.tigr.antware.shared.command.events.StartEvent;
import org.tigr.antware.shared.command.events.SubmitEvent;
import org.tigr.antware.shared.command.events.SuspendEvent;

/**
 * The <b>CommandImp</b> is a base implementation of the interface
 * ICommand. Most command types can just sub-class CommandImp and
 * add the functionality that they need instead of having to
 * re-implement ICommand.  CommandImp handles everything in ICommand
 * in the most generic way and mostly handles the getters/setters.
 * CommandImp also provides a base implementation of event handling.
 *
 * Many of the data values meaning depends on the command type
 * and what IRunner ends up running the command.  The descriptions
 * here are generic but should be better described by the class that
 * either sub-classes this class or wraps this class.
 */
public abstract class CommandImp implements ICommand {

    /** The field <code>log</code> */
    static private Logger log = Logger.getLogger(CommandImp.class);

    // handles event dispatching

    /** The field <code>disp</code> */
    protected HTCEventDispatcher disp = new HTCEventDispatcher();

    // environment

    /** The field <code>env</code> */
    protected HashMap<String, String> env;

    // All Command variables

    /** The field <code>id</code> */
    protected long id;

    /** The field <code>name</code> */
    protected String name;

    /** The field <code>submitURL</code> */
    protected String submitURL;

    /** The field <code>type</code> */
    protected String type;

    /** The field <code>state</code> */
    protected CmdState state;

    /** The field <code>submitTime</code> */
    protected long submitTime = -1;

    /** The field <code>startTime</code> */
    protected long startTime = -1;
    
    /** The field <code>endTime</code> */
    protected long endTime = -1;

    /** The field <code>username</code> */
    protected String username;

    /** The field <code>group</code> */
    protected String group;

    /** The field <code>email</code> */
    protected String email;

    /** The field <code>notifyScript</code> */
    protected String notifyScript;

    /** The field <code>waitUntil</code> */
    protected Date waitUntil;

    /**
     * Create a new <code>CommandImp</code> instance with default values.
     *
     */
    public CommandImp() {
        state = CmdState.INIT;
        id = 0;    // uninitialized Command id
        group = "global";
        username = "nobody";
        waitUntil = new Date();    // now
    }

    /**
     * The method <code>setAllFields</code> copies all the fields to the passed copy. If
     * the property is a complex property then a clone is made
     *
     * @param copy
     */
    protected void setAllFields(CommandImp copy) {
        if (env != null) copy.env = (HashMap<String, String>) env.clone();

        copy.id = id;
        copy.name = name;
        copy.submitURL = submitURL;
        copy.type = type;
        copy.state = state;

        if (submitTime != 0) copy.submitTime = submitTime;

        if (startTime != 0) copy.startTime = startTime;

        if (endTime != 0) copy.endTime = endTime;

        copy.username = username;
        copy.group = group;
        copy.email = email;
        copy.notifyScript = notifyScript;

        if(waitUntil != null) copy.waitUntil = (Date) waitUntil.clone();
    }

    /**
     * The method <code>isFinished<code>
     *
     * @return
     *
     */
    public boolean isFinished() {
        if ((state == CmdState.FINISHED) || (state == CmdState.FAILED)
                || (state == CmdState.INTERRUPTED)) {
            return true;
        }
        else {
            return false;
        }
    }

    // 
    // Setters & Getters
    // 

    /**
     * Get the username value.
     *
     * @return the username value.
     */
    public String getUserName() {
        return username;
    }

    /**
     * Set the username value.
     *
     * @param pUsername
     *            The new Username value.
     */
    public void setUserName(String pUsername) {
        username = pUsername;
    }

    /**
     * Get the submitURL
     *
     * @return the submitURL
     */
    public String getSubmitURL() {
        return submitURL;
    }

    /**
     * Set the submitURL value.
     *
     * @param pSubmitURL
     *            The new submitURL value.
     */
    public void setSubmitURL(String pSubmitURL) {
        submitURL = pSubmitURL;
    }

    /**
     * Get the group value.
     *
     * @return the group value.
     */
    public String getGroup() {
        return group;
    }

    /**
     * Set the Group value.
     *
     * @param pGroup The new Group value.
     */
    public void setGroup(String pGroup) {
        this.group = pGroup;
    }

    /**
     * <code>getEmail</code> get email address associated with request if it needs to
     * be different than the username.
     *
     * @return a <code>String</code> value
     */
    public String getEmail() {
        return email;
    }

    /**
     * The method <code>setWaitUntil<code>
     *
     *
     * @param pWaituntil
     *
     */
    public void setWaitUntil(Date pWaituntil) {
        waitUntil = pWaituntil;
    }

    /**
     * The method <code>getWaitUntil<code>
     *
     *
     * @return
     *
     */
    public Date getWaitUntil() {
        return waitUntil;
    }

    /**
     * <code>setEmail</code> set the email address associated with the request if it is
     * needed to be different than the default.
     *
     * @param pEmail a <code>String</code> value
     */
    public void setEmail(String pEmail) {
        email = pEmail;
    }

    /**
     * <code>getNotifyScript</code> get script to notify when command is finished.
     *
     * @return a <code>String</code> value
     */
    public String getNotifyScript() {
        return notifyScript;
    }

    /**
     * <code>setNotifyScript</code> set script to call when command is finished. The
     * script is executed and should expect the request id, email address, and return
     * value passed to it.
     * 
     * @param pNotifyScript
     */
    public void setNotifyScript(String pNotifyScript) {

        log.debug("Set notify script to " + pNotifyScript);

        notifyScript = pNotifyScript;
    }

    /**
     * Get the name value.
     *
     * @return the name value.
     */
    public String getName() {
        return name;
    }

    /**
     * Set the Name value.
     *
     * @param pName The new Name value.
     */
    public void setName(String pName) {
        this.name = pName;
    }

    /**
     * <code>getType</code> get the Command type.
     *
     * @return a <code>String</code> value
     */
    public String getType() {
        return type;
    }

    /**
     * <code>setType</code> set the Command type.
     *
     * @param p_type
     *            a <code>String</code> value
     */
    public void setType(String p_type) {
        type = p_type;
    }

    /**
     * <code>getID</code> get Command id.
     *
     * @return an <code>long</code> Command id
     */
    public long getID() {

        log.debug(" get id ->" + id);

        return this.id;
    }

    /**
     * <code>setID</code> set Command id.
     *
     * @param pId a <code>long</code> Command id
     */
    public void setID(long pId) {

        log.debug("Set id " + pId);

        this.id = pId;
    }

    /**
     * <code>setSubmitTime</code> set the submit time.
     *
     * @param pSubmitTime a <code>Date</code> of the submit time
     */
    public void setSubmitTime(long pSubmitTime) {
        this.submitTime = pSubmitTime;
    }

    /**
     * <code>getSubmitTime</code> get the submit time.
     *
     * @return a <code>Date</code> of the submit time
     */
    public long getSubmitTime() {
        return this.submitTime;
    }

    /**
     * <code>setStartTime</code> set the start time.
     *
     * @param pStartTime a <code>Date</code> of start time value
     */
    public void setStartTime(long pStartTime) {
        this.startTime = pStartTime;
    }

    /**
     * <code>getStartTime</code> get the start time.
     *
     * @return a <code>Date</code> of start time
     */
    public long getStartTime() {
        return this.startTime;
    }

    /**
     * <code>setEndTime</code> set the end time
     *
     * @param pEndTime a <code>Date</code> of end time
     */
    public void setEndTime(long pEndTime) {
        this.endTime = pEndTime;
    }

    /**
     * <code>getEndTime</code> get the end time
     *
     * @return a <code>Date</code> value
     */
    public long getEndTime() {
        return this.endTime;
    }

    /**
     * <code>setState</code> set the command state.
     *
     * @param pState a <code>String</code> value
     */
    public void setState(CmdState pState) {
    	if (log.isDebugEnabled()) {
    		log.debug("Setting the state (as object) to: " + pState.toString());
    	}
        this.state = pState;
    }

    /**
     * Describe <code>setState</code> method here.
     *
     * @param p_state
     *            a <code>String</code> value
     */
    public void setState(String p_state) {
        if (log.isDebugEnabled()) {
            log.debug("Setting the state to: " + p_state);
        }
        this.state = CmdState.getState(p_state);
        if (log.isDebugEnabled()) {
            log.debug("State set to: " + this.state);
        }
    }

    /**
     * <code>getStateAsString</code> used by XML marshaller. returns state as string
     * instead of a CmdState object.
     *
     * @return a <code>String</code> value
     */
    public String getStateAsString() {
        return this.state.toString();
    }

    /**
     * The method <code>getState<code>
     *
     *
     * @return
     *
     */
    public CmdState getState() {
        return this.state;
    }

    /**
     * Get the Env value.
     *
     * @return the Env value.
     */
    public Map getEnv() {
        return env;
    }

    /**
     * Set the Env value.
     *
     * @param p_Env The new environment.
     *
     * @param pEnv
     */
    public void setEnv(Map pEnv) {
        this.env = (HashMap<String, String>) pEnv;
    }

    // 
    // Event Handling methods
    // 

    /**
     * <code>addSubmitListener</code>.
     *
     * @param pListener a <code>CommandSubmitLI</code> value
     */
    public void addRuntimeStatusListener(CommandRuntimeStatusLI pListener) {
        log.debug("Adding a Runtimestatus listener");
        disp.addRuntimeStatusListener(pListener);
    }

    /**
     * <code>removeSubmitListener</code>.
     *
     * @param pListener a <code>CommandSubmitLI</code> value
     */
    public void removeRuntimeStatusListener(CommandRuntimeStatusLI pListener) {
        disp.removeRuntimeStatusListener(pListener);
    }

    /**
     * <code>addLifetimeListener</code>.
     *
     * @param pListener a <code>CommandLifetimeLI</code> value
     */
    public void addLifetimeListener(CommandLifetimeLI pListener) {
        disp.addLifetimeListener(pListener);
    }

    /**
     * <code>removeLifetimeListener</code>.
     *
     * @param pListener a <code>CommandLifetimeLI</code> value
     */
    public void removeLifetimeListener(CommandLifetimeLI pListener) {
        disp.removeLifetimeListener(pListener);
    }

    /**
     * <code>addStatusListener</code>.
     *
     * @param pListener
     *            a <code>CommandStatusListener</code> value
     */
    public void addStatusListener(CommandStatusLI pListener) {
        log.debug("Adding a status listener");
        disp.addStatusListener(pListener);
    }

    /**
     * <code>removeStatusListener</code>
     *
     * @param pListener
     *            a <code>CommandStatusListener</code> value
     */
    public void removeStatusListener(CommandStatusLI pListener) {
        disp.removeStatusListener(pListener);
    }

    /**
     * The method <code>addTaskListener<code>
     *
     *
     * @param pListener
     *
     */
    public void addTaskListener(CommandTaskLI pListener) {
        log.debug("Adding a Task listener");
        disp.addTaskListener(pListener);
    }

    /**
     * The method <code>removeTaskListener<code>
     *
     * @param pListener
     *
     */
    public void removeTaskListener(CommandTaskLI pListener) {
        log.debug("Removing a Task listener");
        disp.removeTaskListener(pListener);
    }

    /**
     * <code>fireSubmitEvent</code> Notify all listeners that have registered interest
     * for notification on this event type.
     *
     * @param pMachine
     * @param pLog
     */
    public void fireSubmitEvent(String pMachine, String pLog) {
        log.debug("building Submit Event ");

        Date ldate = new Date();
        String message = "Command(" + id + ") was submitted by " + username + " on "
                         + pMachine + " at " + ldate;
        SubmitEvent event = new SubmitEvent(this, id, ldate, message, pMachine, username,
                                            pLog);

        this.fireSubmitEvent(event);
    }

    /**
     * <code>fireSubmitEvent</code> Notify all listeners that have registered interest
     * for notification on this event type.
     *
     * @param pEvent
     */
    public synchronized void fireSubmitEvent(SubmitEvent pEvent) {
    	log.info("Command event: Submit for " + id);

		submitTime = pEvent.getDate().getTime();
        state = CmdState.WAITING;

        log.debug(" Changed state to waiting now let all listerners know");
        disp.fireSubmitEvent(pEvent);
    }

    /**
     * <code>fireStartEvent</code> Notify all listeners that have registered interest
     * for notification on this event type .
     *
     * @param pMachine
     */
    public synchronized void fireStartEvent(String pMachine) {
        Date ldate = new Date();
        String message = "Command(" + id + ") was started on " + pMachine + " at " + ldate;
        StartEvent event = new StartEvent(this, id, ldate, message, pMachine);

        this.fireStartEvent(event);
    }

    /**
     * <code>fireStartEvent</code>. Notify all listeners that have registered interest
     * for notification on this event type.
     *
     * @param pEvent
     *            a <code>StartEvent</code> value
     */
    public synchronized void fireStartEvent(StartEvent pEvent) {
    	log.info("Command event: Start for " + id);

        state = CmdState.RUNNING;
        startTime = pEvent.getDate().getTime();

        disp.fireStartEvent(pEvent);
    }

    /**
     * <code>fireFinishEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     *
     * @param pFinishEv
     *            a <code>FinishEvent</code> value
     */
    public synchronized void fireFinishEvent(FinishEvent pFinishEv) {
	log.info("Command event: Finish for " + id);

        //endTime = (Date) p_finishEv.getDate().clone();
		endTime = pFinishEv.getDate().getTime();
        state = CmdState.FINISHED;

        disp.fireFinishEvent(pFinishEv);
    }

    /**
     * <code>fireInterruptEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     *
     * @param pInterruptEv
     *            an <code>InterruptEvent</code> value
     */
    public void fireInterruptEvent(InterruptEvent pInterruptEv) {
    	log.info("Command Event: Interrupt for " + id);
        state = CmdState.INTERRUPTED;

        disp.fireInterruptEvent(pInterruptEv);
    }

    /**
     * The method <code>fireInterruptEvent<code>
     *
     * @param pMessage
     * @param pType
     *
     */
    public void fireInterruptEvent(String pMessage, CommandInterrupt pType) {
        log.debug("Command build fire interrupt event");

        InterruptEvent ie = new InterruptEvent((Object) this, id, new Date(), pMessage,
                                               pType);

        state = CmdState.INTERRUPTED;

        disp.fireInterruptEvent(ie);
    }

    /**
     * <code>fireResumeEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     *
     * @param pResumeEv
     *            a <code>ResumeEvent</code> value
     */
    public void fireResumeEvent(ResumeEvent pResumeEv) {
	log.info("Command Event: Resume for " + id);
        state = CmdState.RUNNING;

        disp.fireResumeEvent(pResumeEv);
    }

    /**
     * The method <code>fireRestartEvent<code>
     *
     *
     * @param pStartEv
     *
     */
    public void fireRestartEvent(StartEvent pStartEv) {
	log.info("Command Event: Restart for " + id);
        disp.fireRestartEvent(pStartEv);
    }

    /**
     * The method <code>fireFailureEvent<code>
     *
     *
     * @param pFailureEv
     *
     */
    public void fireFailureEvent(FailureEvent pFailureEv) {
    	log.info("Command Event: Failure for " + id);

		endTime = pFailureEv.getDate().getTime();
        state = CmdState.FAILED;

        disp.fireFailureEvent(pFailureEv);
    }

    /**
     * The method <code>fireFailureEvent<code>
     *
     * @param pException
     *
     */
    public void fireFailureEvent(Throwable pException) {
        log.info("Build failure event ");

        FailureEvent event = new FailureEvent(this, id, new Date(),
                                              pException.getMessage(), pException);
        log.error("Firing failure event ");
        this.fireFailureEvent(event);
        log.error("Done firing a failure event ");
    }

    /**
     * The method <code>fireSuspendEvent<code>
     *
     * @param pSuspendEv
     *
     */
    public void fireSuspendEvent(SuspendEvent pSuspendEv) {
	log.info("Command event: Suspend for " + id);
        disp.fireSuspendEvent(pSuspendEv);
    }
}
